To Do

Overview

Summary

Photography has been a hobby of mine for most of my life, and I found a particular niche in abstract photography, specifically multi-exposure images. This background inspired me to find mathematical ways to analyze my photo library as a whole, with a special focus on color trends and affinities.

Business Application

The processes used in this project have a business application within a mobile app. By evaluating a user’s camera roll, the app could discern favorite colors and suggest products that match that color profile.

Data Collection Method

Flickr API

  • Worked backwards from the present to separate generic images (downloads, memes, screenshots, videos) from photos (robust EXIF data)
  • Collected 4500 usable flickr IDs (more IDs meant a larger date range to sample from)
    • (Python script was used to randomly select from the mega-list)
  • Selected IDs had their EXIF data collected (again) and jpg downloaded at “XL” size
  • EXIF data was saved to its own CSV

Python

  • Each photo was mathematically divided into 9 subimages, to allow for full-photo trends to be compared against center-image trends
    • (All calculations were done on each photo 10 times, once for the full photo, and once for each sub-image. In retrospect, much of the sub-image processing was redundant and could have been gathered via subsetting the full-image matrix)
  • Gamma adjustment (RGB linearization)
  • Conversion to HSL for “readable” values (colorsys library)
  • Segmentation (“posterization”) (pymeanshift library)

Note:

Shout out to my buddy Phil! He was a great resource for feedback and encouragement as I formulated my processing script, but also donated runtime on his computer and processed 250 images used in this dataset.

PyMeanShift/Segmentation

A photograph with 4000 pixels may have 4000 different color values represented. I wanted to “clump” pixels with similar colors in the same area of an image into a single color value. PyMeanShift accomplishes this by taking in the image and three numerical variables: spatial radius, range radius, and minimum density. These refer to maximum color difference, maximum placement difference, and minimum “clump” size, respectively.

https://ieeexplore.ieee.org/document/1000236

Datapoints Collected

EXIF

FlickrID - unique identifier for each photo

DateTimeOriginal/CreateDate/ModifyDate - attempted to capture whether the images were edited on the phone (unsuccessful)

Software - iOS version or mobile app used for photo capture

LensInfo/ LensModel - data on which phone lens capture the photo

JFIFVersion - compression marker applied by some 3rd party apps. Disappears when image is edited in native iOS photos app.

ISO - light sensitivity setting

ExposureTime - in seconds (fractions)

FNumber - aperture

FocalLength- Fixed to LensInfo/LensModel

FocalLengthIn35mmFormat - iOS interpretation of zoom level

BrightnessValue - Auto-generated brightness value

SubjectArea -  Coordinate values generated by iOS (not directly relevant to this project, but captured for future use)

Image Data

A python class was used to gather image data as attributes, then dumped to a csv with vars(). 

All relevant attributes/variables described below

using_id - Flickr ID

img_width - in pixels

img_height - in pixels

do_img_at - timestamp for evaluating processing time 

sub_img - 0 for whole image, 1 for top-left, 2 for middle-left, 5 for center, etc.

full_id - concat of flickrID and sub_image to form unique identifier.

RGB Overview Statistics

(r/g/b)_min - (3 columns) Minimum red/green/blue channel value in the whole image

(r/g/b)_max - (3 columns) Maximum red/green/blue channel value

(r/g/b)_mean - (3 columns) Average red/green/blue channel value

(r/g/b)_mode - (3 columns) count of common red/green/blue channel value (forgot to capture its value :facepalm: (in my attempt to capture the value, I neglected to reset the index of the pandas dataSeries))

center_rgb - (tuple) R/G/B value of the pixel mathematically in the center of the image

Next segment of columns captured from segmented/posterized image

post_num_regions - number of color “clumps” after processing

post_top_hsl - (tuple) most common pixel value

post_top_count - quantity of most common pixel value

post_(2-6)_hsl - (5 columns)(tuples) next most common pixel values, in descending order of frequency

post_(2-6)_count - (5 columns) counts for their respective common pixel values

center_hsl - (tuple) HSL value of the pixel mathematically in the center of the image

Hue color banding was done by subjective eyeball measurement

All hues: red, orange, yellow, green, cyan, blue, purple, magenta

full_(hue)_count - count of all pixels that fell within the hue band, regardless of saturation and lightness

visib_(hue)_count - count of pixels in the hue band deemed as “visibly [hue]” (saturation over 40%, lightness between 20% and 75%)

vivid_(hue)_count - count of pixels in the hue band deemed as “vividly [hue]” (saturation over 70%, lightness between 30% and 70%)

Saturation Statistics

sat_min_val - lowest saturation value in image

sat_25_val - 25% quartile value

sat_50_val - median saturation

sat_75_val - 75% quartile value

sat_max_val - most saturation

HSL Mean Values

hue_mean_val - average hue value (not incredibly meaningful on a looping spectrum)

sat_mean_val - average saturation value

light_mean_val - average brightness

Lightness Statistics

light_max_val - brightest value

light_max_count - quantity of pixels within 1.5% (literal) of the max lightness value

light_min_val - darkest value

light_min_count - quantity of pixels within 1.5% (literal) of he minimum lightness value (darkest)

light_25_value - 25% quartile value

light_50_value - median brightness

light_75_value - 75% quartile value

gen_bright_count - quantity of pixels with over 85% lightness

gen_dark_count - quantity of pixels with under 15% brightness

common_hsl_(1-4)_val - (4 columns)(tuple) four most common HSL values

common_hsl_(1-4)_count - (4 columns) quantities of the four most common HSL values

Data Collation

Due to collecting image processing data on multiple computers, multiple files were created for exif and image data – partly by design and party due to occasional read/write conflicts on shared files. All records were gathered into Excel and checked for duplicates before exporting as CSVs.

Hypotheses

H1 - Hue vs Date

\(Ho:\) There is no correlation between time of year and color values

\(Ha:\) Warm color values are more prominent between May and September

H2 - Lightness vs Time

\(Ho:\) There is no correlation between time of day and lightness values

\(Ha:\) Lightness values are higher between 6 am and 6pm

H3 - Saturation vs Subject

\(Ho:\) There is no correlation between saturation and being a picture of my cat

\(Ha:\) Low saturation values are increasingly common over time, especially in central sub-images

H4 - Vividness vs Image Type

\(Ho:\) Vivid ratio (percentage of vivid pixels) is uniformly distributed among all Software types

\(Ha:\) Vivid ratio is consistently highest in Slow Shutter Cam photos without JFIF values

R

Preparing the Data

Imports

Cleaning

Pre Import:
Sub-image data for main images (0) was bugged in the first hours of image processing. This was fixed in Excel during the data collation stage.

EXIF:

  • Fix column headers
  • Drop columns: date_time_original, modify_date, lens_info, f_number, focal_length
  • NA values - JFIF <- 0,  subject_area <- >depends on data type< “0 0 0 0”
# names(exif) <- gsub("([a-z0-9])([A-Z])", "\\1_\\2", names(exif))
# names(exif) <- names(exif) %>% tolower()

exif_tidy <- select(exif, -c(date_time_original, modify_date, lens_info, fnumber, focal_length))
exif_tidy <- replace_na(exif_tidy, list(subject_area = "0 0 0 0", jfifversion = 0))

Img_data:

  • Drop columns: flickr, img_loc, the_image, crop_coords, r_mode, b_mode, g_mode, img_height, img_width, do_img_at
  • Na values - post_2-6_hsl <- (-1,-1,-1)
imgsd_tidy <- select(img_data, -c(flickr, img_loc, the_image, img_width, img_height, crop_coords, do_img_at, r_mode, b_mode, g_mode))

imgsd_tidy <- replace_na(imgsd_tidy, list(
  post_2_hsl = "(-1, -1, -1)",
  post_3_hsl = "(-1, -1, -1)",
  post_4_hsl = "(-1, -1, -1)",
  post_5_hsl = "(-1, -1, -1)",
  post_6_hsl = "(-1, -1, -1)"
  )
  )

Basic Feature Engineering

EXIF

  • Split date_time_original to year, month-day, time columns
  • Date column uses a placeholder year so month-to-month comparisons are consistent

# exif_tidy <- exif_tidy %>% separate(create_date, into = c('date', 'time'), sep = " ", remove = TRUE) %>% separate(date, into = c('year', 'month', 'day'), sep = ":") 

exif_tidy$date <- as.Date(paste("1881", exif_tidy$month, exif_tidy$day, sep = "-"), format ="%Y-%m-%d")

Img_data

  • Count by flickr id

    • Add count(flickr_id) to EXIF
    • Flag counts under 10
    • Flag counts under 6 (ie: subimage 5 not available)
    • Output list of all flickrIDs with less than 10 records for additional (future) processing

subimg_qty <- imgsd_tidy %>% count(using_id)

To my (happy) surprise, only 5 images have less than 10 results and only 2 have less than 6. In the interest of time, I’m noting these IDs by hand and simply removing them from my working data


good_ids <- subimg_qty[subimg_qty$n >=6, "using_id"]

imgsd_tidy <- imgsd_tidy %>% filter(using_id %in% good_ids$using_id)

exif_tidy <- exif_tidy %>% filter(flickr_id %in% good_ids$using_id)
  • Total pixels = full_(hue)_count(s) (dimension data incorrect for first 1400 records)

imgsd_tidy <- imgsd_tidy %>% mutate(total_pixels = full_red_count +
                                      full_orange_count +
                                      full_yellow_count +
                                      full_green_count +
                                      full_cyan_count +
                                      full_blue_count + 
                                      full_purple_count +
                                      full_mag_count)
  • Split pixel lists/tuples for 3-d mapping
imgsd_tidy$center_hsl <- str_replace(imgsd_tidy$center_hsl, '\\[|\\]', '')

imgsd_tidy <- imgsd_tidy %>% mutate_all(~ gsub('\\(|\\)', '', .))

imgsd_split <- imgsd_tidy %>% 
  separate(center_rgb, 
           into = c('center_r', 'center_g', 'center_b'), 
           sep = ',') %>%
  separate(post_top_hsl, 
           into = c('post_top_hue', 'post_top_sat', 'post_top_light'), 
           sep = ',') %>%
  separate(post_2_hsl, 
           into = c('post_2_hue', 'post_2_sat', 'post_2_light'), 
           sep = ',') %>%
  separate(post_3_hsl, 
           into = c('post_3_hue', 'post_3_sat', 'post_3_light'), 
           sep = ',') %>%  
  separate(post_4_hsl, 
           into = c('post_4_hue', 'post_4_sat', 'post_4_light'), 
           sep = ',') %>%
  separate(post_5_hsl, 
           into = c('post_5_hue', 'post_5_sat', 'post_5_light'), 
           sep = ',') %>%
  separate(post_6_hsl, 
           into = c('post_6_hue', 'post_6_sat', 'post_6_light'), 
           sep = ',') %>%
  separate(center_hsl, 
           into = c('center_hue', 'center_sat', 'center_light'), 
           sep = ',') %>%
  separate(common_hsl_1_val, 
           into = c('common_hsl_1_hue', 'common_hsl_1_sat', 'common_hsl_1_light'), 
           sep = ',') %>%
  separate(common_hsl_2_val, 
           into = c('common_hsl_2_hue', 'common_hsl_2_sat', 'common_hsl_2_light'), 
           sep = ',') %>%
  separate(common_hsl_3_val, 
           into = c('common_hsl_3_hue', 'common_hsl_3_sat', 'common_hsl_3_light'), 
           sep = ',') %>%
  separate(common_hsl_4_val, 
           into = c('common_hsl_4_hue', 'common_hsl_4_sat', 'common_hsl_4_light'), 
           sep = ',')

I probably should have saved those independently during the python image processing stage.

  • To be added as needed:

    • Using total_pixels to calculate specific ratios

Exploration

3D Scatter

https://plotly.com/r/3d-scatter-plots/

  • Center rgb
fig <- plot_ly(imgsd_split, x=~center_r, y= ~center_g, z= ~center_b, 
               type = "scatter3d", mode="markers", size = 1, color = ~sub_img)

fig
  • Rgb mean

fig <- plot_ly(imgsd_split, x=~r_mean, y= ~g_mean, z= ~b_mean,
               type = "scatter3d", mode="markers", size = 2, color = ~sub_img)

fig
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
  • Hsl mean

fig <- plot_ly(imgsd_split, x=~hue_mean_val, y= ~sat_mean_val, z= ~light_mean_val,
               type = "scatter3d", mode="markers", size = 1, color = ~sub_img)

fig
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
  • Post_top_hsl
fig <- plot_ly(imgsd_split, x=~post_top_hue, y= ~post_top_sat, z= ~post_top_light,
               type = "scatter3d", mode="markers", size = 2, color = ~sub_img)

fig
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
  • Common_hsl_1_vals

fig <- plot_ly(imgsd_split, 
               x=~common_hsl_1_hue, 
               y= ~common_hsl_1_sat, 
               z= ~common_hsl_1_light,
               type = "scatter3d", mode="markers", size = 2, color = ~sub_img)

fig
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
  • Common_hsl_2_vals
fig <- plot_ly(imgsd_split, 
               x=~common_hsl_2_hue, 
               y= ~common_hsl_2_sat, 
               z= ~common_hsl_2_light,
               type = "scatter3d", mode="markers", size = 2, color = ~sub_img)

fig
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors

fig <- plot_ly(imgsd_split, 
               x=~common_hsl_3_hue, 
               y= ~common_hsl_3_sat, 
               z= ~common_hsl_3_light,
               type = "scatter3d", mode="markers", size = 2, color = ~sub_img)

fig
Warning: Ignoring 2 observationsWarning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: Ignoring 2 observationsWarning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
fig <- plot_ly(imgsd_split, 
               x=~common_hsl_4_hue, 
               y= ~common_hsl_4_sat, 
               z= ~common_hsl_4_light,
               type = "scatter3d", mode="markers", size = 2, color = ~sub_img)

fig
Warning: Ignoring 2 observationsWarning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: Ignoring 2 observationsWarning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors

2D Scatter


fig <- plot_ly(imgsd_split, 
               x=~common_hsl_4_hue, 
               y= ~common_hsl_4_sat, 
               type = "scatter", mode="markers", size = 2, color = ~sub_img)
  • Light min x light max
fig <- plot_ly(imgsd_split, 
               x=~light_min_val, 
               y= ~light_max_val, 
               type = "scatter", mode="markers", size = 2, color = ~sub_img)

fig
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
  • Gen bright x gen dark
imgsd_split$gen_bright_count <- as.integer(imgsd_split$gen_bright_count)


imgsd_split$gen_dark_count <- as.integer(imgsd_split$gen_dark_count)

fig <- plot_ly(imgsd_split, 
               x=~gen_bright_count, 
               y= ~gen_dark_count, 
               type = "scatter", mode="markers", size = 2, color = ~sub_img)

fig
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
  • Sat min x sat max

# imgsd_split$gen_bright_count <- as.integer(imgsd_split$gen_bright_count)


# imgsd_split$gen_dark_count <- as.integer(imgsd_split$gen_dark_count)

fig <- plot_ly(imgsd_split, 
               x=~sat_min_val, 
               y= ~sat_max_val, 
               type = "scatter", mode="markers", size = 2, color = ~sub_img)

fig
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning: n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors

1D Histograms

  • Vivid %
  • Post-num (filter by img segment)
  • Exif brightness
  • Mean_lightness
  • (r/g/b) mean
  • Date distribution (with and without year)

0D Pie Charts

  • Full color bands
  • Vis color bands
  • Vivid color bands

Analysis

-trim exif down to id/date-time/software/brightness info

Left join to add date/software data from exif to img_data

*do facets with sub_img numbers*

H1

H2

H3

H4

Conclusion

plot(cars)

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

LS0tDQp0aXRsZTogIkRhdGFzZXQgQ2Fwc3RvbmUiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIFRvIERvDQoNCi0gICBGaXggbGlua3MNCg0KLSAgIE1ha2UgYSBnaWYgb2YgcHltZWFuc2hpZnQgc2FtcGxlcyB3aXRoIHZhcmlhYmxlcy9zdGF0cw0KDQotICAgVmlzdWFsaXplIGNvbG9yIHZhbHVlcw0KDQojIE92ZXJ2aWV3DQoNCiMjIFN1bW1hcnkNCg0KUGhvdG9ncmFwaHkgaGFzIGJlZW4gYSBob2JieSBvZiBtaW5lIGZvciBtb3N0IG9mIG15IGxpZmUsIGFuZCBJIGZvdW5kIGEgcGFydGljdWxhciBuaWNoZSBpbiBhYnN0cmFjdCBwaG90b2dyYXBoeSwgc3BlY2lmaWNhbGx5IG11bHRpLWV4cG9zdXJlIGltYWdlcy4gVGhpcyBiYWNrZ3JvdW5kIGluc3BpcmVkIG1lIHRvIGZpbmQgbWF0aGVtYXRpY2FsIHdheXMgdG8gYW5hbHl6ZSBteSBwaG90byBsaWJyYXJ5IGFzIGEgd2hvbGUsIHdpdGggYSBzcGVjaWFsIGZvY3VzIG9uIGNvbG9yIHRyZW5kcyBhbmQgYWZmaW5pdGllcy4NCg0KIyMgQnVzaW5lc3MgQXBwbGljYXRpb24NCg0KVGhlIHByb2Nlc3NlcyB1c2VkIGluIHRoaXMgcHJvamVjdCBoYXZlIGEgYnVzaW5lc3MgYXBwbGljYXRpb24gd2l0aGluIGEgbW9iaWxlIGFwcC4gQnkgZXZhbHVhdGluZyBhIHVzZXIncyBjYW1lcmEgcm9sbCwgdGhlIGFwcCBjb3VsZCBkaXNjZXJuIGZhdm9yaXRlIGNvbG9ycyBhbmQgc3VnZ2VzdCBwcm9kdWN0cyB0aGF0IG1hdGNoIHRoYXQgY29sb3IgcHJvZmlsZS4NCg0KIyMgRGF0YSBDb2xsZWN0aW9uIE1ldGhvZA0KDQojIyMgRmxpY2tyIEFQSQ0KDQotICAgV29ya2VkIGJhY2t3YXJkcyBmcm9tIHRoZSBwcmVzZW50IHRvIHNlcGFyYXRlIGdlbmVyaWMgaW1hZ2VzIChkb3dubG9hZHMsIG1lbWVzLCBzY3JlZW5zaG90cywgdmlkZW9zKSBmcm9tIHBob3RvcyAocm9idXN0IEVYSUYgZGF0YSkNCi0gICBDb2xsZWN0ZWQgNDUwMCB1c2FibGUgZmxpY2tyIElEcyAobW9yZSBJRHMgbWVhbnQgYSBsYXJnZXIgZGF0ZSByYW5nZSB0byBzYW1wbGUgZnJvbSkNCiAgICAtICAgKFB5dGhvbiBzY3JpcHQgd2FzIHVzZWQgdG8gcmFuZG9tbHkgc2VsZWN0IGZyb20gdGhlIG1lZ2EtbGlzdCkNCi0gICBTZWxlY3RlZCBJRHMgaGFkIHRoZWlyIEVYSUYgZGF0YSBjb2xsZWN0ZWQgKGFnYWluKSBhbmQganBnIGRvd25sb2FkZWQgYXQgIlhMIiBzaXplDQotICAgRVhJRiBkYXRhIHdhcyBzYXZlZCB0byBpdHMgb3duIENTVg0KDQojIyMgUHl0aG9uDQoNCi0gICBFYWNoIHBob3RvIHdhcyBtYXRoZW1hdGljYWxseSBkaXZpZGVkIGludG8gOSBzdWJpbWFnZXMsIHRvIGFsbG93IGZvciBmdWxsLXBob3RvIHRyZW5kcyB0byBiZSBjb21wYXJlZCBhZ2FpbnN0IGNlbnRlci1pbWFnZSB0cmVuZHMNCiAgICAtICAgKEFsbCBjYWxjdWxhdGlvbnMgd2VyZSBkb25lIG9uIGVhY2ggcGhvdG8gMTAgdGltZXMsIG9uY2UgZm9yIHRoZSBmdWxsIHBob3RvLCBhbmQgb25jZSBmb3IgZWFjaCBzdWItaW1hZ2UuIEluIHJldHJvc3BlY3QsIG11Y2ggb2YgdGhlIHN1Yi1pbWFnZSBwcm9jZXNzaW5nIHdhcyByZWR1bmRhbnQgYW5kIGNvdWxkIGhhdmUgYmVlbiBnYXRoZXJlZCB2aWEgc3Vic2V0dGluZyB0aGUgZnVsbC1pbWFnZSBtYXRyaXgpDQotICAgR2FtbWEgYWRqdXN0bWVudCAoUkdCIGxpbmVhcml6YXRpb24pDQogICAgLSAgIFtodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9TUkdCI1RyYW5zZmVyX2Z1bmN0aW9uXF8oJTIyZ1xgYW1tYSUyMl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvU1JHQiNUcmFuc2Zlcl9mdW5jdGlvbl8oJTIyZyU2MGFtbWElMjIpJTdCLnVyaSU3RCkNCi0gICBDb252ZXJzaW9uIHRvIEhTTCBmb3IgInJlYWRhYmxlIiB2YWx1ZXMgKGNvbG9yc3lzIGxpYnJhcnkpDQotICAgU2VnbWVudGF0aW9uICgicG9zdGVyaXphdGlvbiIpIChweW1lYW5zaGlmdCBsaWJyYXJ5KQ0KICAgIC0gICA8aHR0cHM6Ly9naXRodWIuY29tL2ZqZWFuL3B5bWVhbnNoaWZ0Pg0KDQoqKk5vdGUqKjoNCg0KU2hvdXQgb3V0IHRvIG15IGJ1ZGR5IFBoaWwhIEhlIHdhcyBhIGdyZWF0IHJlc291cmNlIGZvciBmZWVkYmFjayBhbmQgZW5jb3VyYWdlbWVudCBhcyBJIGZvcm11bGF0ZWQgbXkgcHJvY2Vzc2luZyBzY3JpcHQsIGJ1dCBhbHNvIGRvbmF0ZWQgcnVudGltZSBvbiBoaXMgY29tcHV0ZXIgYW5kIHByb2Nlc3NlZCAyNTAgaW1hZ2VzIHVzZWQgaW4gdGhpcyBkYXRhc2V0Lg0KDQojIyMjICoqUHlNZWFuU2hpZnQvU2VnbWVudGF0aW9uKioNCg0KQSBwaG90b2dyYXBoIHdpdGggNDAwMCBwaXhlbHMgbWF5IGhhdmUgNDAwMCBkaWZmZXJlbnQgY29sb3IgdmFsdWVzIHJlcHJlc2VudGVkLiBJIHdhbnRlZCB0byAiY2x1bXAiIHBpeGVscyB3aXRoIHNpbWlsYXIgY29sb3JzIGluIHRoZSBzYW1lIGFyZWEgb2YgYW4gaW1hZ2UgaW50byBhIHNpbmdsZSBjb2xvciB2YWx1ZS4gUHlNZWFuU2hpZnQgYWNjb21wbGlzaGVzIHRoaXMgYnkgdGFraW5nIGluIHRoZSBpbWFnZSBhbmQgdGhyZWUgbnVtZXJpY2FsIHZhcmlhYmxlczogc3BhdGlhbCByYWRpdXMsIHJhbmdlIHJhZGl1cywgYW5kIG1pbmltdW0gZGVuc2l0eS4gVGhlc2UgcmVmZXIgdG8gbWF4aW11bSBjb2xvciBkaWZmZXJlbmNlLCBtYXhpbXVtIHBsYWNlbWVudCBkaWZmZXJlbmNlLCBhbmQgbWluaW11bSAiY2x1bXAiIHNpemUsIHJlc3BlY3RpdmVseS4NCg0KPGh0dHBzOi8vaWVlZXhwbG9yZS5pZWVlLm9yZy9kb2N1bWVudC8xMDAwMjM2Pg0KDQojIyBEYXRhcG9pbnRzIENvbGxlY3RlZA0KDQojIyMgRVhJRg0KDQoqKkZsaWNrcklEKiogLSB1bmlxdWUgaWRlbnRpZmllciBmb3IgZWFjaCBwaG90bw0KDQoqKkRhdGVUaW1lT3JpZ2luYWwvQ3JlYXRlRGF0ZS9Nb2RpZnlEYXRlKiogLSBhdHRlbXB0ZWQgdG8gY2FwdHVyZSB3aGV0aGVyIHRoZSBpbWFnZXMgd2VyZSBlZGl0ZWQgb24gdGhlIHBob25lICh1bnN1Y2Nlc3NmdWwpDQoNCioqU29mdHdhcmUqKiAtIGlPUyB2ZXJzaW9uIG9yIG1vYmlsZSBhcHAgdXNlZCBmb3IgcGhvdG8gY2FwdHVyZQ0KDQoqKkxlbnNJbmZvLyBMZW5zTW9kZWwqKiAtIGRhdGEgb24gd2hpY2ggcGhvbmUgbGVucyBjYXB0dXJlIHRoZSBwaG90bw0KDQoqKkpGSUZWZXJzaW9uKiogLSBjb21wcmVzc2lvbiBtYXJrZXIgYXBwbGllZCBieSBzb21lIDNyZCBwYXJ0eSBhcHBzLiBEaXNhcHBlYXJzIHdoZW4gaW1hZ2UgaXMgZWRpdGVkIGluIG5hdGl2ZSBpT1MgcGhvdG9zIGFwcC4NCg0KKipJU08qKiAtIGxpZ2h0IHNlbnNpdGl2aXR5IHNldHRpbmcNCg0KKipFeHBvc3VyZVRpbWUqKiAtIGluIHNlY29uZHMgKGZyYWN0aW9ucykNCg0KKipGTnVtYmVyKiogLSBhcGVydHVyZQ0KDQoqKkZvY2FsTGVuZ3RoKiotIEZpeGVkIHRvIExlbnNJbmZvL0xlbnNNb2RlbA0KDQoqKkZvY2FsTGVuZ3RoSW4zNW1tRm9ybWF0KiogLSBpT1MgaW50ZXJwcmV0YXRpb24gb2Ygem9vbSBsZXZlbA0KDQoqKkJyaWdodG5lc3NWYWx1ZSoqIC0gQXV0by1nZW5lcmF0ZWQgYnJpZ2h0bmVzcyB2YWx1ZQ0KDQoqKlN1YmplY3RBcmVhKiogLcKgIENvb3JkaW5hdGUgdmFsdWVzIGdlbmVyYXRlZCBieSBpT1MgKG5vdCBkaXJlY3RseSByZWxldmFudCB0byB0aGlzIHByb2plY3QsIGJ1dCBjYXB0dXJlZCBmb3IgZnV0dXJlIHVzZSkNCg0KIyMjIEltYWdlIERhdGENCg0KQSBweXRob24gY2xhc3Mgd2FzIHVzZWQgdG8gZ2F0aGVyIGltYWdlIGRhdGEgYXMgYXR0cmlidXRlcywgdGhlbiBkdW1wZWQgdG8gYSBjc3Ygd2l0aCB2YXJzKCkuwqANCg0KQWxsIHJlbGV2YW50IGF0dHJpYnV0ZXMvdmFyaWFibGVzIGRlc2NyaWJlZCBiZWxvdw0KDQoqKnVzaW5nX2lkKiogLSBGbGlja3IgSUQNCg0KKippbWdfd2lkdGgqKiAtIGluIHBpeGVscw0KDQoqKmltZ19oZWlnaHQqKiAtIGluIHBpeGVscw0KDQoqKmRvX2ltZ19hdCoqIC0gdGltZXN0YW1wIGZvciBldmFsdWF0aW5nIHByb2Nlc3NpbmcgdGltZcKgDQoNCioqc3ViX2ltZyoqIC0gMCBmb3Igd2hvbGUgaW1hZ2UsIDEgZm9yIHRvcC1sZWZ0LCAyIGZvciBtaWRkbGUtbGVmdCwgNSBmb3IgY2VudGVyLCBldGMuDQoNCioqZnVsbF9pZCoqIC0gY29uY2F0IG9mIGZsaWNrcklEIGFuZCBzdWJfaW1hZ2UgdG8gZm9ybSB1bmlxdWUgaWRlbnRpZmllci4NCg0KKioqUkdCIE92ZXJ2aWV3IFN0YXRpc3RpY3MqKioNCg0KKiooci9nL2IpXF9taW4qKiAtICooMyBjb2x1bW5zKSogTWluaW11bSByZWQvZ3JlZW4vYmx1ZSBjaGFubmVsIHZhbHVlIGluIHRoZSB3aG9sZSBpbWFnZQ0KDQoqKihyL2cvYilcX21heCoqIC0gKigzIGNvbHVtbnMpKiBNYXhpbXVtIHJlZC9ncmVlbi9ibHVlIGNoYW5uZWwgdmFsdWUNCg0KKiooci9nL2IpXF9tZWFuKiogLSAqKDMgY29sdW1ucykqIEF2ZXJhZ2UgcmVkL2dyZWVuL2JsdWUgY2hhbm5lbCB2YWx1ZQ0KDQoqKihyL2cvYilcX21vZGUqKiAtICooMyBjb2x1bW5zKSogY291bnQgb2YgY29tbW9uIHJlZC9ncmVlbi9ibHVlIGNoYW5uZWwgdmFsdWUgKGZvcmdvdCB0byBjYXB0dXJlIGl0cyB2YWx1ZSA6ZmFjZXBhbG06IChpbiBteSBhdHRlbXB0IHRvIGNhcHR1cmUgdGhlIHZhbHVlLCBJIG5lZ2xlY3RlZCB0byByZXNldCB0aGUgaW5kZXggb2YgdGhlIHBhbmRhcyBkYXRhU2VyaWVzKSkNCg0KKipjZW50ZXJfcmdiKiogLSAqKHR1cGxlKSogUi9HL0IgdmFsdWUgb2YgdGhlIHBpeGVsIG1hdGhlbWF0aWNhbGx5IGluIHRoZSBjZW50ZXIgb2YgdGhlIGltYWdlDQoNCioqKk5leHQgc2VnbWVudCBvZiBjb2x1bW5zIGNhcHR1cmVkIGZyb20gc2VnbWVudGVkL3Bvc3Rlcml6ZWQgaW1hZ2UqKioNCg0KKipwb3N0X251bV9yZWdpb25zKiogLSBudW1iZXIgb2YgY29sb3IgImNsdW1wcyIgYWZ0ZXIgcHJvY2Vzc2luZw0KDQoqKnBvc3RfdG9wX2hzbCoqIC0gKHR1cGxlKSBtb3N0IGNvbW1vbiBwaXhlbCB2YWx1ZQ0KDQoqKnBvc3RfdG9wX2NvdW50KiogLSBxdWFudGl0eSBvZiBtb3N0IGNvbW1vbiBwaXhlbCB2YWx1ZQ0KDQoqKnBvc3RcXygyLTYpXF9oc2wqKiAtICooNSBjb2x1bW5zKSh0dXBsZXMpKiBuZXh0IG1vc3QgY29tbW9uIHBpeGVsIHZhbHVlcywgaW4gZGVzY2VuZGluZyBvcmRlciBvZiBmcmVxdWVuY3kNCg0KKipwb3N0XF8oMi02KVxfY291bnQqKiAtICooNSBjb2x1bW5zKSogY291bnRzIGZvciB0aGVpciByZXNwZWN0aXZlIGNvbW1vbiBwaXhlbCB2YWx1ZXMNCg0KKipjZW50ZXJfaHNsKiogLSAqKHR1cGxlKSogSFNMIHZhbHVlIG9mIHRoZSBwaXhlbCBtYXRoZW1hdGljYWxseSBpbiB0aGUgY2VudGVyIG9mIHRoZSBpbWFnZQ0KDQoqKipIdWUgY29sb3IgYmFuZGluZyB3YXMgZG9uZSBieSBzdWJqZWN0aXZlIGV5ZWJhbGwgbWVhc3VyZW1lbnQqKioNCg0KKioqQWxsIGh1ZXM6IHJlZCwgb3JhbmdlLCB5ZWxsb3csIGdyZWVuLCBjeWFuLCBibHVlLCBwdXJwbGUsIG1hZ2VudGEqKioNCg0KKipmdWxsXF8oaHVlKVxfY291bnQqKiAtIGNvdW50IG9mIGFsbCBwaXhlbHMgdGhhdCBmZWxsIHdpdGhpbiB0aGUgaHVlIGJhbmQsIHJlZ2FyZGxlc3Mgb2Ygc2F0dXJhdGlvbiBhbmQgbGlnaHRuZXNzDQoNCioqdmlzaWJcXyhodWUpXF9jb3VudCoqIC0gY291bnQgb2YgcGl4ZWxzIGluIHRoZSBodWUgYmFuZCBkZWVtZWQgYXMgInZpc2libHkgW2h1ZV0iIChzYXR1cmF0aW9uIG92ZXIgNDAlLCBsaWdodG5lc3MgYmV0d2VlbiAyMCUgYW5kIDc1JSkNCg0KKip2aXZpZFxfKGh1ZSlcX2NvdW50KiogLSBjb3VudCBvZiBwaXhlbHMgaW4gdGhlIGh1ZSBiYW5kIGRlZW1lZCBhcyAidml2aWRseSBbaHVlXSIgKHNhdHVyYXRpb24gb3ZlciA3MCUsIGxpZ2h0bmVzcyBiZXR3ZWVuIDMwJSBhbmQgNzAlKQ0KDQoqKipTYXR1cmF0aW9uIFN0YXRpc3RpY3MqKioNCg0KKipzYXRfbWluX3ZhbCoqIC0gbG93ZXN0IHNhdHVyYXRpb24gdmFsdWUgaW4gaW1hZ2UNCg0KKipzYXRfMjVfdmFsKiogLSAyNSUgcXVhcnRpbGUgdmFsdWUNCg0KKipzYXRfNTBfdmFsKiogLSBtZWRpYW4gc2F0dXJhdGlvbg0KDQoqKnNhdF83NV92YWwqKiAtIDc1JSBxdWFydGlsZSB2YWx1ZQ0KDQoqKnNhdF9tYXhfdmFsKiogLSBtb3N0IHNhdHVyYXRpb24NCg0KKioqSFNMIE1lYW4gVmFsdWVzKioqDQoNCioqaHVlX21lYW5fdmFsKiogLSBhdmVyYWdlIGh1ZSB2YWx1ZSAobm90IGluY3JlZGlibHkgbWVhbmluZ2Z1bCBvbiBhIGxvb3Bpbmcgc3BlY3RydW0pDQoNCioqc2F0X21lYW5fdmFsKiogLSBhdmVyYWdlIHNhdHVyYXRpb24gdmFsdWUNCg0KKipsaWdodF9tZWFuX3ZhbCoqIC0gYXZlcmFnZSBicmlnaHRuZXNzDQoNCioqKkxpZ2h0bmVzcyBTdGF0aXN0aWNzKioqDQoNCioqbGlnaHRfbWF4X3ZhbCoqIC0gYnJpZ2h0ZXN0IHZhbHVlDQoNCioqbGlnaHRfbWF4X2NvdW50KiogLSBxdWFudGl0eSBvZiBwaXhlbHMgd2l0aGluIDEuNSUgKGxpdGVyYWwpIG9mIHRoZSBtYXggbGlnaHRuZXNzIHZhbHVlDQoNCioqbGlnaHRfbWluX3ZhbCoqIC0gZGFya2VzdCB2YWx1ZQ0KDQoqKmxpZ2h0X21pbl9jb3VudCoqIC0gcXVhbnRpdHkgb2YgcGl4ZWxzIHdpdGhpbiAxLjUlIChsaXRlcmFsKSBvZiBoZSBtaW5pbXVtIGxpZ2h0bmVzcyB2YWx1ZSAoZGFya2VzdCkNCg0KKipsaWdodF8yNV92YWx1ZSoqIC0gMjUlIHF1YXJ0aWxlIHZhbHVlDQoNCioqbGlnaHRfNTBfdmFsdWUqKiAtIG1lZGlhbiBicmlnaHRuZXNzDQoNCioqbGlnaHRfNzVfdmFsdWUqKiAtIDc1JSBxdWFydGlsZSB2YWx1ZQ0KDQoqKmdlbl9icmlnaHRfY291bnQqKiAtIHF1YW50aXR5IG9mIHBpeGVscyB3aXRoIG92ZXIgODUlIGxpZ2h0bmVzcw0KDQoqKmdlbl9kYXJrX2NvdW50KiogLSBxdWFudGl0eSBvZiBwaXhlbHMgd2l0aCB1bmRlciAxNSUgYnJpZ2h0bmVzcw0KDQoqKmNvbW1vbl9oc2xcXygxLTQpXF92YWwqKiAtICooNCBjb2x1bW5zKSh0dXBsZSkqIGZvdXIgbW9zdCBjb21tb24gSFNMIHZhbHVlcw0KDQoqKmNvbW1vbl9oc2xcXygxLTQpXF9jb3VudCoqIC0gKig0IGNvbHVtbnMpKiBxdWFudGl0aWVzIG9mIHRoZSBmb3VyIG1vc3QgY29tbW9uIEhTTCB2YWx1ZXMNCg0KIyMjIyBEYXRhIENvbGxhdGlvbg0KDQpEdWUgdG8gY29sbGVjdGluZyBpbWFnZSBwcm9jZXNzaW5nIGRhdGEgb24gbXVsdGlwbGUgY29tcHV0ZXJzLCBtdWx0aXBsZSBmaWxlcyB3ZXJlIGNyZWF0ZWQgZm9yIGV4aWYgYW5kIGltYWdlIGRhdGEgLS0gcGFydGx5IGJ5IGRlc2lnbiBhbmQgcGFydHkgZHVlIHRvIG9jY2FzaW9uYWwgcmVhZC93cml0ZSBjb25mbGljdHMgb24gc2hhcmVkIGZpbGVzLiBBbGwgcmVjb3JkcyB3ZXJlIGdhdGhlcmVkIGludG8gRXhjZWwgYW5kIGNoZWNrZWQgZm9yIGR1cGxpY2F0ZXMgYmVmb3JlIGV4cG9ydGluZyBhcyBDU1ZzLg0KDQojIEh5cG90aGVzZXMNCg0KIyMjIEgxIC0gSHVlIHZzIERhdGUNCg0KJEhvOiQgVGhlcmUgaXMgbm8gY29ycmVsYXRpb24gYmV0d2VlbiB0aW1lIG9mIHllYXIgYW5kIGNvbG9yIHZhbHVlcw0KDQokSGE6JCBXYXJtIGNvbG9yIHZhbHVlcyBhcmUgbW9yZSBwcm9taW5lbnQgYmV0d2VlbiBNYXkgYW5kIFNlcHRlbWJlcg0KDQojIyMgSDIgLSBMaWdodG5lc3MgdnMgVGltZQ0KDQokSG86JCBUaGVyZSBpcyBubyBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRpbWUgb2YgZGF5IGFuZCBsaWdodG5lc3MgdmFsdWVzDQoNCiRIYTokIExpZ2h0bmVzcyB2YWx1ZXMgYXJlIGhpZ2hlciBiZXR3ZWVuIDYgYW0gYW5kIDZwbQ0KDQojIyMgSDMgLSBTYXR1cmF0aW9uIHZzIFN1YmplY3QNCg0KJEhvOiQgVGhlcmUgaXMgbm8gY29ycmVsYXRpb24gYmV0d2VlbiBzYXR1cmF0aW9uIGFuZCBiZWluZyBhIHBpY3R1cmUgb2YgbXkgY2F0DQoNCiRIYTokIExvdyBzYXR1cmF0aW9uIHZhbHVlcyBhcmUgaW5jcmVhc2luZ2x5IGNvbW1vbiBvdmVyIHRpbWUsIGVzcGVjaWFsbHkgaW4gY2VudHJhbCBzdWItaW1hZ2VzDQoNCiMjIyBINCAtIFZpdmlkbmVzcyB2cyBJbWFnZSBUeXBlDQoNCiRIbzokIFZpdmlkIHJhdGlvIChwZXJjZW50YWdlIG9mIHZpdmlkIHBpeGVscykgaXMgdW5pZm9ybWx5IGRpc3RyaWJ1dGVkIGFtb25nIGFsbCBTb2Z0d2FyZSB0eXBlcw0KDQokSGE6JCBWaXZpZCByYXRpbyBpcyBjb25zaXN0ZW50bHkgaGlnaGVzdCBpbiBTbG93IFNodXR0ZXIgQ2FtIHBob3RvcyB3aXRob3V0IEpGSUYgdmFsdWVzDQoNCiMgUg0KDQojIyBQcmVwYXJpbmcgdGhlIERhdGENCg0KIyMjIEltcG9ydHMNCg0KYGBge3J9DQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShwbG90bHkpDQoNCmV4aWYgPC0gcmVhZF9jc3YoImNhcHN0b25lX2V4aWYuY3N2IikNCmltZ19kYXRhIDwtIHJlYWRfY3N2KCJjYXBzdG9uZV9pbWdfZGF0YS5jc3YiKQ0KDQpzcGVjKGV4aWYpDQpzcGVjKGltZ19kYXRhKQ0KYGBgDQoNCiMjIyBDbGVhbmluZw0KDQoqKlByZSBJbXBvcnQ6KipcDQpTdWItaW1hZ2UgZGF0YSBmb3IgbWFpbiBpbWFnZXMgKDApIHdhcyBidWdnZWQgaW4gdGhlIGZpcnN0IGhvdXJzIG9mIGltYWdlIHByb2Nlc3NpbmcuIFRoaXMgd2FzIGZpeGVkIGluIEV4Y2VsIGR1cmluZyB0aGUgZGF0YSBjb2xsYXRpb24gc3RhZ2UuDQoNCioqRVhJRjoqKg0KDQotICAgRml4IGNvbHVtbiBoZWFkZXJzDQotICAgRHJvcCBjb2x1bW5zOiBkYXRlX3RpbWVfb3JpZ2luYWwsIG1vZGlmeV9kYXRlLCBsZW5zX2luZm8sIGZfbnVtYmVyLCBmb2NhbF9sZW5ndGgNCi0gICBOQSB2YWx1ZXMgLSBKRklGIFw8LSAwLMKgIHN1YmplY3RfYXJlYSBcPC0gXD5kZXBlbmRzIG9uIGRhdGEgdHlwZVw8ICIwIDAgMCAwIg0KDQpgYGB7cn0NCiMgbmFtZXMoZXhpZikgPC0gZ3N1YigiKFthLXowLTldKShbQS1aXSkiLCAiXFwxX1xcMiIsIG5hbWVzKGV4aWYpKQ0KIyBuYW1lcyhleGlmKSA8LSBuYW1lcyhleGlmKSAlPiUgdG9sb3dlcigpDQoNCmV4aWZfdGlkeSA8LSBzZWxlY3QoZXhpZiwgLWMoZGF0ZV90aW1lX29yaWdpbmFsLCBtb2RpZnlfZGF0ZSwgbGVuc19pbmZvLCBmbnVtYmVyLCBmb2NhbF9sZW5ndGgpKQ0KZXhpZl90aWR5IDwtIHJlcGxhY2VfbmEoZXhpZl90aWR5LCBsaXN0KHN1YmplY3RfYXJlYSA9ICIwIDAgMCAwIiwgamZpZnZlcnNpb24gPSAwKSkNCg0KYGBgDQoNCioqSW1nX2RhdGE6KioNCg0KLSAgIERyb3AgY29sdW1uczogZmxpY2tyLCBpbWdfbG9jLCB0aGVfaW1hZ2UsIGNyb3BfY29vcmRzLCByX21vZGUsIGJfbW9kZSwgZ19tb2RlLCBpbWdfaGVpZ2h0LCBpbWdfd2lkdGgsIGRvX2ltZ19hdA0KLSAgIE5hIHZhbHVlcyAtIHBvc3RfMi02X2hzbCBcPC0gKC0xLC0xLC0xKQ0KDQpgYGB7cn0NCmltZ3NkX3RpZHkgPC0gc2VsZWN0KGltZ19kYXRhLCAtYyhmbGlja3IsIGltZ19sb2MsIHRoZV9pbWFnZSwgaW1nX3dpZHRoLCBpbWdfaGVpZ2h0LCBjcm9wX2Nvb3JkcywgZG9faW1nX2F0LCByX21vZGUsIGJfbW9kZSwgZ19tb2RlKSkNCg0KaW1nc2RfdGlkeSA8LSByZXBsYWNlX25hKGltZ3NkX3RpZHksIGxpc3QoDQogIHBvc3RfMl9oc2wgPSAiKC0xLCAtMSwgLTEpIiwNCiAgcG9zdF8zX2hzbCA9ICIoLTEsIC0xLCAtMSkiLA0KICBwb3N0XzRfaHNsID0gIigtMSwgLTEsIC0xKSIsDQogIHBvc3RfNV9oc2wgPSAiKC0xLCAtMSwgLTEpIiwNCiAgcG9zdF82X2hzbCA9ICIoLTEsIC0xLCAtMSkiDQogICkNCiAgKQ0KDQpgYGANCg0KIyMjIEJhc2ljIEZlYXR1cmUgRW5naW5lZXJpbmcNCg0KKipFWElGKioNCg0KLSAgIFNwbGl0IGRhdGVfdGltZV9vcmlnaW5hbCB0byB5ZWFyLCBtb250aC1kYXksIHRpbWUgY29sdW1ucw0KLSAgIERhdGUgY29sdW1uIHVzZXMgYSBwbGFjZWhvbGRlciB5ZWFyIHNvIG1vbnRoLXRvLW1vbnRoIGNvbXBhcmlzb25zIGFyZSBjb25zaXN0ZW50DQoNCmBgYHtyfQ0KDQpleGlmX3RpZHkgPC0gZXhpZl90aWR5ICU+JSBzZXBhcmF0ZShjcmVhdGVfZGF0ZSwgaW50byA9IGMoJ2RhdGUnLCAndGltZScpLCBzZXAgPSAiICIsIHJlbW92ZSA9IFRSVUUpICU+JSBzZXBhcmF0ZShkYXRlLCBpbnRvID0gYygneWVhcicsICdtb250aCcsICdkYXknKSwgc2VwID0gIjoiKSANCg0KZXhpZl90aWR5JGRhdGUgPC0gYXMuRGF0ZShwYXN0ZSgiMTg4MSIsIGV4aWZfdGlkeSRtb250aCwgZXhpZl90aWR5JGRheSwgc2VwID0gIi0iKSwgZm9ybWF0ID0iJVktJW0tJWQiKQ0KYGBgDQoNCioqSW1nX2RhdGEqKg0KDQotICAgQ291bnQgYnkgZmxpY2tyIGlkDQoNCiAgICAtICAgQWRkIGNvdW50KGZsaWNrcl9pZCkgdG8gRVhJRg0KICAgIC0gICBGbGFnIGNvdW50cyB1bmRlciAxMA0KICAgIC0gICBGbGFnIGNvdW50cyB1bmRlciA2IChpZTogc3ViaW1hZ2UgNSBub3QgYXZhaWxhYmxlKQ0KICAgIC0gICBPdXRwdXQgbGlzdCBvZiBhbGwgZmxpY2tySURzIHdpdGggbGVzcyB0aGFuIDEwIHJlY29yZHMgZm9yIGFkZGl0aW9uYWwgKGZ1dHVyZSkgcHJvY2Vzc2luZw0KDQpgYGB7cn0NCg0Kc3ViaW1nX3F0eSA8LSBpbWdzZF90aWR5ICU+JSBjb3VudCh1c2luZ19pZCkNCg0KYGBgDQoNClRvIG15IChoYXBweSkgc3VycHJpc2UsIG9ubHkgNSBpbWFnZXMgaGF2ZSBsZXNzIHRoYW4gMTAgcmVzdWx0cyBhbmQgb25seSAyIGhhdmUgbGVzcyB0aGFuIDYuIEluIHRoZSBpbnRlcmVzdCBvZiB0aW1lLCBJJ20gbm90aW5nIHRoZXNlIElEcyBieSBoYW5kIGFuZCBzaW1wbHkgcmVtb3ZpbmcgdGhlbSBmcm9tIG15IHdvcmtpbmcgZGF0YQ0KDQpgYGB7cn0NCg0KZ29vZF9pZHMgPC0gc3ViaW1nX3F0eVtzdWJpbWdfcXR5JG4gPj02LCAidXNpbmdfaWQiXQ0KDQppbWdzZF90aWR5IDwtIGltZ3NkX3RpZHkgJT4lIGZpbHRlcih1c2luZ19pZCAlaW4lIGdvb2RfaWRzJHVzaW5nX2lkKQ0KDQpleGlmX3RpZHkgPC0gZXhpZl90aWR5ICU+JSBmaWx0ZXIoZmxpY2tyX2lkICVpbiUgZ29vZF9pZHMkdXNpbmdfaWQpDQoNCmBgYA0KDQotICAgVG90YWwgcGl4ZWxzID0gZnVsbFxfKGh1ZSlcX2NvdW50KHMpIChkaW1lbnNpb24gZGF0YSBpbmNvcnJlY3QgZm9yIGZpcnN0IDE0MDAgcmVjb3JkcykNCg0KYGBge3J9DQoNCmltZ3NkX3RpZHkgPC0gaW1nc2RfdGlkeSAlPiUgbXV0YXRlKHRvdGFsX3BpeGVscyA9IGZ1bGxfcmVkX2NvdW50ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVsbF9vcmFuZ2VfY291bnQgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsX3llbGxvd19jb3VudCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGxfZ3JlZW5fY291bnQgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsX2N5YW5fY291bnQgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsX2JsdWVfY291bnQgKyANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVsbF9wdXJwbGVfY291bnQgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsX21hZ19jb3VudCkNCg0KYGBgDQoNCi0gICBTcGxpdCBwaXhlbCBsaXN0cy90dXBsZXMgZm9yIDMtZCBtYXBwaW5nDQoNCmBgYHtyfQ0KaW1nc2RfdGlkeSRjZW50ZXJfaHNsIDwtIHN0cl9yZXBsYWNlKGltZ3NkX3RpZHkkY2VudGVyX2hzbCwgJ1xcW3xcXF0nLCAnJykNCg0KaW1nc2RfdGlkeSA8LSBpbWdzZF90aWR5ICU+JSBtdXRhdGVfYWxsKH4gZ3N1YignXFwofFxcKScsICcnLCAuKSkNCg0KaW1nc2Rfc3BsaXQgPC0gaW1nc2RfdGlkeSAlPiUgDQogIHNlcGFyYXRlKGNlbnRlcl9yZ2IsIA0KICAgICAgICAgICBpbnRvID0gYygnY2VudGVyX3InLCAnY2VudGVyX2cnLCAnY2VudGVyX2InKSwgDQogICAgICAgICAgIHNlcCA9ICcsJykgJT4lDQogIHNlcGFyYXRlKHBvc3RfdG9wX2hzbCwgDQogICAgICAgICAgIGludG8gPSBjKCdwb3N0X3RvcF9odWUnLCAncG9zdF90b3Bfc2F0JywgJ3Bvc3RfdG9wX2xpZ2h0JyksIA0KICAgICAgICAgICBzZXAgPSAnLCcpICU+JQ0KICBzZXBhcmF0ZShwb3N0XzJfaHNsLCANCiAgICAgICAgICAgaW50byA9IGMoJ3Bvc3RfMl9odWUnLCAncG9zdF8yX3NhdCcsICdwb3N0XzJfbGlnaHQnKSwgDQogICAgICAgICAgIHNlcCA9ICcsJykgJT4lDQogIHNlcGFyYXRlKHBvc3RfM19oc2wsIA0KICAgICAgICAgICBpbnRvID0gYygncG9zdF8zX2h1ZScsICdwb3N0XzNfc2F0JywgJ3Bvc3RfM19saWdodCcpLCANCiAgICAgICAgICAgc2VwID0gJywnKSAlPiUgIA0KICBzZXBhcmF0ZShwb3N0XzRfaHNsLCANCiAgICAgICAgICAgaW50byA9IGMoJ3Bvc3RfNF9odWUnLCAncG9zdF80X3NhdCcsICdwb3N0XzRfbGlnaHQnKSwgDQogICAgICAgICAgIHNlcCA9ICcsJykgJT4lDQogIHNlcGFyYXRlKHBvc3RfNV9oc2wsIA0KICAgICAgICAgICBpbnRvID0gYygncG9zdF81X2h1ZScsICdwb3N0XzVfc2F0JywgJ3Bvc3RfNV9saWdodCcpLCANCiAgICAgICAgICAgc2VwID0gJywnKSAlPiUNCiAgc2VwYXJhdGUocG9zdF82X2hzbCwgDQogICAgICAgICAgIGludG8gPSBjKCdwb3N0XzZfaHVlJywgJ3Bvc3RfNl9zYXQnLCAncG9zdF82X2xpZ2h0JyksIA0KICAgICAgICAgICBzZXAgPSAnLCcpICU+JQ0KICBzZXBhcmF0ZShjZW50ZXJfaHNsLCANCiAgICAgICAgICAgaW50byA9IGMoJ2NlbnRlcl9odWUnLCAnY2VudGVyX3NhdCcsICdjZW50ZXJfbGlnaHQnKSwgDQogICAgICAgICAgIHNlcCA9ICcsJykgJT4lDQogIHNlcGFyYXRlKGNvbW1vbl9oc2xfMV92YWwsIA0KICAgICAgICAgICBpbnRvID0gYygnY29tbW9uX2hzbF8xX2h1ZScsICdjb21tb25faHNsXzFfc2F0JywgJ2NvbW1vbl9oc2xfMV9saWdodCcpLCANCiAgICAgICAgICAgc2VwID0gJywnKSAlPiUNCiAgc2VwYXJhdGUoY29tbW9uX2hzbF8yX3ZhbCwgDQogICAgICAgICAgIGludG8gPSBjKCdjb21tb25faHNsXzJfaHVlJywgJ2NvbW1vbl9oc2xfMl9zYXQnLCAnY29tbW9uX2hzbF8yX2xpZ2h0JyksIA0KICAgICAgICAgICBzZXAgPSAnLCcpICU+JQ0KICBzZXBhcmF0ZShjb21tb25faHNsXzNfdmFsLCANCiAgICAgICAgICAgaW50byA9IGMoJ2NvbW1vbl9oc2xfM19odWUnLCAnY29tbW9uX2hzbF8zX3NhdCcsICdjb21tb25faHNsXzNfbGlnaHQnKSwgDQogICAgICAgICAgIHNlcCA9ICcsJykgJT4lDQogIHNlcGFyYXRlKGNvbW1vbl9oc2xfNF92YWwsIA0KICAgICAgICAgICBpbnRvID0gYygnY29tbW9uX2hzbF80X2h1ZScsICdjb21tb25faHNsXzRfc2F0JywgJ2NvbW1vbl9oc2xfNF9saWdodCcpLCANCiAgICAgICAgICAgc2VwID0gJywnKQ0KDQpgYGANCg0KSSBwcm9iYWJseSBzaG91bGQgaGF2ZSBzYXZlZCB0aG9zZSBpbmRlcGVuZGVudGx5IGR1cmluZyB0aGUgcHl0aG9uIGltYWdlIHByb2Nlc3Npbmcgc3RhZ2UuDQoNCi0gICBUbyBiZSBhZGRlZCBhcyBuZWVkZWQ6DQoNCiAgICAtICAgVXNpbmcgdG90YWxfcGl4ZWxzIHRvIGNhbGN1bGF0ZSBzcGVjaWZpYyByYXRpb3MNCg0KIyMgRXhwbG9yYXRpb24NCg0KIyMjIDNEIFNjYXR0ZXINCg0KPGh0dHBzOi8vcGxvdGx5LmNvbS9yLzNkLXNjYXR0ZXItcGxvdHMvPg0KDQotICAgQ2VudGVyIHJnYg0KDQpgYGB7cn0NCmZpZyA8LSBwbG90X2x5KGltZ3NkX3NwbGl0LCB4PX5jZW50ZXJfciwgeT0gfmNlbnRlcl9nLCB6PSB+Y2VudGVyX2IsIA0KICAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlPSJtYXJrZXJzIiwgc2l6ZSA9IDEsIGNvbG9yID0gfnN1Yl9pbWcpDQoNCmZpZw0KYGBgDQoNCi0gICBSZ2IgbWVhbg0KDQpgYGB7cn0NCg0KZmlnIDwtIHBsb3RfbHkoaW1nc2Rfc3BsaXQsIHg9fnJfbWVhbiwgeT0gfmdfbWVhbiwgej0gfmJfbWVhbiwNCiAgICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZT0ibWFya2VycyIsIHNpemUgPSAyLCBjb2xvciA9IH5zdWJfaW1nKQ0KDQpmaWcNCmBgYA0KDQotICAgSHNsIG1lYW4NCg0KYGBge3J9DQoNCmZpZyA8LSBwbG90X2x5KGltZ3NkX3NwbGl0LCB4PX5odWVfbWVhbl92YWwsIHk9IH5zYXRfbWVhbl92YWwsIHo9IH5saWdodF9tZWFuX3ZhbCwNCiAgICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZT0ibWFya2VycyIsIHNpemUgPSAxLCBjb2xvciA9IH5zdWJfaW1nKQ0KDQpmaWcNCg0KYGBgDQoNCi0gICBQb3N0X3RvcF9oc2wNCg0KYGBge3J9DQpmaWcgPC0gcGxvdF9seShpbWdzZF9zcGxpdCwgeD1+cG9zdF90b3BfaHVlLCB5PSB+cG9zdF90b3Bfc2F0LCB6PSB+cG9zdF90b3BfbGlnaHQsDQogICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsIG1vZGU9Im1hcmtlcnMiLCBzaXplID0gMiwgY29sb3IgPSB+c3ViX2ltZykNCg0KZmlnDQpgYGANCg0KLSAgIENvbW1vbl9oc2xfMV92YWxzDQoNCmBgYHtyfQ0KDQpmaWcgPC0gcGxvdF9seShpbWdzZF9zcGxpdCwgDQogICAgICAgICAgICAgICB4PX5jb21tb25faHNsXzFfaHVlLCANCiAgICAgICAgICAgICAgIHk9IH5jb21tb25faHNsXzFfc2F0LCANCiAgICAgICAgICAgICAgIHo9IH5jb21tb25faHNsXzFfbGlnaHQsDQogICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsIG1vZGU9Im1hcmtlcnMiLCBzaXplID0gMiwgY29sb3IgPSB+c3ViX2ltZykNCg0KZmlnDQoNCmBgYA0KDQotICAgQ29tbW9uX2hzbF8yX3ZhbHMNCg0KYGBge3J9DQpmaWcgPC0gcGxvdF9seShpbWdzZF9zcGxpdCwgDQogICAgICAgICAgICAgICB4PX5jb21tb25faHNsXzJfaHVlLCANCiAgICAgICAgICAgICAgIHk9IH5jb21tb25faHNsXzJfc2F0LCANCiAgICAgICAgICAgICAgIHo9IH5jb21tb25faHNsXzJfbGlnaHQsDQogICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsIG1vZGU9Im1hcmtlcnMiLCBzaXplID0gMiwgY29sb3IgPSB+c3ViX2ltZykNCg0KZmlnDQpgYGANCg0KYGBge3J9DQoNCmZpZyA8LSBwbG90X2x5KGltZ3NkX3NwbGl0LCANCiAgICAgICAgICAgICAgIHg9fmNvbW1vbl9oc2xfM19odWUsIA0KICAgICAgICAgICAgICAgeT0gfmNvbW1vbl9oc2xfM19zYXQsIA0KICAgICAgICAgICAgICAgej0gfmNvbW1vbl9oc2xfM19saWdodCwNCiAgICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZT0ibWFya2VycyIsIHNpemUgPSAyLCBjb2xvciA9IH5zdWJfaW1nKQ0KDQpmaWcNCmBgYA0KDQpgYGB7cn0NCmZpZyA8LSBwbG90X2x5KGltZ3NkX3NwbGl0LCANCiAgICAgICAgICAgICAgIHg9fmNvbW1vbl9oc2xfNF9odWUsIA0KICAgICAgICAgICAgICAgeT0gfmNvbW1vbl9oc2xfNF9zYXQsIA0KICAgICAgICAgICAgICAgej0gfmNvbW1vbl9oc2xfNF9saWdodCwNCiAgICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZT0ibWFya2VycyIsIHNpemUgPSAyLCBjb2xvciA9IH5zdWJfaW1nKQ0KDQpmaWcNCmBgYA0KDQojIyMgMkQgU2NhdHRlcg0KDQpgYGB7cn0NCg0KZmlnIDwtIHBsb3RfbHkoaW1nc2Rfc3BsaXQsIA0KICAgICAgICAgICAgICAgeD1+Y29tbW9uX2hzbF80X2h1ZSwgDQogICAgICAgICAgICAgICB5PSB+Y29tbW9uX2hzbF80X3NhdCwgDQogICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIiLCBtb2RlPSJtYXJrZXJzIiwgc2l6ZSA9IDIsIGNvbG9yID0gfnN1Yl9pbWcpDQoNCmBgYA0KDQotICAgTGlnaHQgbWluIHggbGlnaHQgbWF4DQoNCmBgYHtyfQ0KZmlnIDwtIHBsb3RfbHkoaW1nc2Rfc3BsaXQsIA0KICAgICAgICAgICAgICAgeD1+bGlnaHRfbWluX3ZhbCwgDQogICAgICAgICAgICAgICB5PSB+bGlnaHRfbWF4X3ZhbCwgDQogICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIiLCBtb2RlPSJtYXJrZXJzIiwgc2l6ZSA9IDIsIGNvbG9yID0gfnN1Yl9pbWcpDQoNCmZpZw0KYGBgDQoNCi0gICBHZW4gYnJpZ2h0IHggZ2VuIGRhcmsNCg0KYGBge3J9DQppbWdzZF9zcGxpdCRnZW5fYnJpZ2h0X2NvdW50IDwtIGFzLmludGVnZXIoaW1nc2Rfc3BsaXQkZ2VuX2JyaWdodF9jb3VudCkNCg0KDQppbWdzZF9zcGxpdCRnZW5fZGFya19jb3VudCA8LSBhcy5pbnRlZ2VyKGltZ3NkX3NwbGl0JGdlbl9kYXJrX2NvdW50KQ0KDQpmaWcgPC0gcGxvdF9seShpbWdzZF9zcGxpdCwgDQogICAgICAgICAgICAgICB4PX5nZW5fYnJpZ2h0X2NvdW50LCANCiAgICAgICAgICAgICAgIHk9IH5nZW5fZGFya19jb3VudCwgDQogICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIiLCBtb2RlPSJtYXJrZXJzIiwgc2l6ZSA9IDIsIGNvbG9yID0gfnN1Yl9pbWcpDQoNCmZpZw0KYGBgDQoNCi0gICBTYXQgbWluIHggc2F0IG1heA0KDQpgYGB7cn0NCg0KZmlnIDwtIHBsb3RfbHkoaW1nc2Rfc3BsaXQsIA0KICAgICAgICAgICAgICAgeD1+c2F0X21pbl92YWwsIA0KICAgICAgICAgICAgICAgeT0gfnNhdF9tYXhfdmFsLCANCiAgICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlciIsIG1vZGU9Im1hcmtlcnMiLCBzaXplID0gMiwgY29sb3IgPSB+c3ViX2ltZykNCg0KZmlnDQoNCmBgYA0KDQojIyMgMUQgSGlzdG9ncmFtcw0KDQotICAgVml2aWQgJQ0KLSAgIFBvc3QtbnVtIChmaWx0ZXIgYnkgaW1nIHNlZ21lbnQpDQotICAgRXhpZiBicmlnaHRuZXNzDQotICAgTWVhbl9saWdodG5lc3MNCi0gICAoci9nL2IpIG1lYW4NCi0gICBEYXRlIGRpc3RyaWJ1dGlvbiAod2l0aCBhbmQgd2l0aG91dCB5ZWFyKQ0KDQojIyMgMEQgUGllIENoYXJ0cw0KDQotICAgRnVsbCBjb2xvciBiYW5kcw0KLSAgIFZpcyBjb2xvciBiYW5kcw0KLSAgIFZpdmlkIGNvbG9yIGJhbmRzDQoNCiMjIEFuYWx5c2lzDQoNCioqLXRyaW0gZXhpZiBkb3duIHRvIGlkL2RhdGUtdGltZS9zb2Z0d2FyZS9icmlnaHRuZXNzIGluZm8qKg0KDQoqKkxlZnQgam9pbiB0byBhZGQgZGF0ZS9zb2Z0d2FyZSBkYXRhIGZyb20gZXhpZiB0byBpbWdfZGF0YSoqDQoNCioqXCpkbyBmYWNldHMgd2l0aCBzdWJfaW1nIG51bWJlcnNcKioqDQoNCiMjIyBIMQ0KDQojIyMgSDINCg0KIyMjIEgzDQoNCiMjIyBINA0KDQojIENvbmNsdXNpb24NCg0KYGBge3J9DQpwbG90KGNhcnMpDQpgYGANCg0KQWRkIGEgbmV3IGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqSW5zZXJ0IENodW5rKiBidXR0b24gb24gdGhlIHRvb2xiYXIgb3IgYnkgcHJlc3NpbmcgKkN0cmwrQWx0K0kqLg0KDQpXaGVuIHlvdSBzYXZlIHRoZSBub3RlYm9vaywgYW4gSFRNTCBmaWxlIGNvbnRhaW5pbmcgdGhlIGNvZGUgYW5kIG91dHB1dCB3aWxsIGJlIHNhdmVkIGFsb25nc2lkZSBpdCAoY2xpY2sgdGhlICpQcmV2aWV3KiBidXR0b24gb3IgcHJlc3MgKkN0cmwrU2hpZnQrSyogdG8gcHJldmlldyB0aGUgSFRNTCBmaWxlKS4NCg0KVGhlIHByZXZpZXcgc2hvd3MgeW91IGEgcmVuZGVyZWQgSFRNTCBjb3B5IG9mIHRoZSBjb250ZW50cyBvZiB0aGUgZWRpdG9yLiBDb25zZXF1ZW50bHksIHVubGlrZSAqS25pdCosICpQcmV2aWV3KiBkb2VzIG5vdCBydW4gYW55IFIgY29kZSBjaHVua3MuIEluc3RlYWQsIHRoZSBvdXRwdXQgb2YgdGhlIGNodW5rIHdoZW4gaXQgd2FzIGxhc3QgcnVuIGluIHRoZSBlZGl0b3IgaXMgZGlzcGxheWVkLg0K